package com.iwhalecloud.citybrain.flow.platform.manager.infrastructure.repository;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import com.iwhalecloud.citybrain.flow.platform.manager.application.common.enums.*;
import com.iwhalecloud.citybrain.flow.platform.manager.application.service.BaseProcessService;
import com.iwhalecloud.citybrain.flow.platform.manager.application.service.impl.task.SyncProcessTaskProcessor;
import com.iwhalecloud.citybrain.flow.platform.manager.common.ErrorCode;
import com.iwhalecloud.citybrain.flow.platform.manager.common.ResponseResult;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.exception.EventCallException;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.FlowProcessInstanceOperateTraceVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.FlowProcessInstanceTraceVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.User;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.log.FlowCommonOperateLogVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.monitor.FlowTaskTraceVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.monitor.ProcessInstanceActionVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.monitor.TaskTraceQueryVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.statistics.ProcessInstanceStatisticsQueryVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.statistics.TaskStatisticsQueryVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.repository.FlowCommonOperateLogRepository;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.repository.FlowProcessInstanceOperateTraceRepository;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.repository.FlowProcessInstanceTraceRepository;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.repository.FlowTaskTraceRepository;
import com.iwhalecloud.citybrain.flow.platform.manager.application.service.ProcessInstanceMonitorService;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.repository.UserRepository;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.flowable.engine.RuntimeService;
import org.flowable.task.api.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author lilh
 * @date 2019-07-26 14:08
 */
@Service
public class ProcessInstanceMonitorServiceImpl extends BaseProcessService implements ProcessInstanceMonitorService {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    private static Map<Integer, String> flagToStatus = new HashMap<Integer, String>() {

        private static final long serialVersionUID = 1438630927243797433L;

        {
            put(0, ProcessInstanceStatusEnum.NORMAL.getCode());
            put(1, ProcessInstanceStatusEnum.WARNING.getCode());
            put(2, ProcessInstanceStatusEnum.TIMEOUT.getCode());
        }
    };

    @Autowired
    private FlowProcessInstanceTraceRepository flowProcessInstanceTraceRepository;

    @Autowired
    private FlowProcessInstanceOperateTraceRepository flowProcessInstanceOperateTraceRepository;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private FlowTaskTraceRepository flowTaskTraceRepository;

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private FlowCommonOperateLogRepository commonOperateLogService;

    @Autowired
    private SyncProcessTaskProcessor syncProcessTaskProcessor;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult doAction(ProcessInstanceActionVo processInstanceActionVo) {

        List<FlowProcessInstanceTraceVo> processInstanceTraces = resolveRunningProcessInstances(processInstanceActionVo.getProcessInstanceIds());

        if (CollectionUtils.isEmpty(processInstanceTraces)) {
            logger.warn("没有需要操作的流程实例");
            return ResponseResult.fail(ErrorCode.NOT_EXIST.errCode(), "没有需要操作的流程实例");
        }

        processInstanceActionVo.setProcessInstanceIds(processInstanceTraces.stream().map(FlowProcessInstanceTraceVo::getProcessInstanceId).collect(Collectors.toSet()));

        return doActionCommon(processInstanceTraces, processInstanceActionVo, true);
    }

    /**
     * @param processInstanceTraces
     * @param processInstanceActionVo
     * @param checkUser               是否校验用户已登录
     * @return
     */
    private ResponseResult doActionCommon(List<FlowProcessInstanceTraceVo> processInstanceTraces, ProcessInstanceActionVo processInstanceActionVo, boolean checkUser) {
        String action = processInstanceActionVo.getAction();
        List<FlowTaskTraceVo> runningTasks = null;

        if (Objects.equals(action, ProcessInstanceActionEnum.PAUSE.getCode())) {
            //挂起
            processInstanceTraces.forEach(item -> {
                item.setStatus(ProcessInstanceStatusEnum.PAUSE.getCode());
                item.setUpdateTime(new Date());
            });

            //实例下运行的任务全部暂停
            updateRunningTaskStatusOrLinkStatus(processInstanceActionVo, processInstanceActionVo.getProcessInstanceIds(), ProcessInstanceStatusEnum.PAUSE.getCode(), null, checkUser);

            //挂起流程实例
            updateProcessInstanceToPause(processInstanceActionVo.getProcessInstanceIds());
        }

        if (Objects.equals(action, ProcessInstanceActionEnum.UNPAUSE.getCode())) {
            //解挂
            processInstanceTraces.forEach(item -> {
                item.setStatus(flagToStatus.get(item.getFlag()));
                item.setUpdateTime(new Date());
            });

            //还原实例状态，根据flag, 还原实例下运行中的任务状态，以flag为准
            updateRunningTaskStatusByFlag(processInstanceActionVo, checkUser);

            //解挂流程实例
            updateProcessInstanceToUnpause(processInstanceActionVo.getProcessInstanceIds());
        }

        if (Objects.equals(action, ProcessInstanceActionEnum.RECALL.getCode())) {
            //撤单
            processInstanceTraces.forEach(item -> {
                item.setLinkStatus(ProcessInstanceLinkStatusEnum.END.getCode());
                item.setStatus(ProcessInstanceStatusEnum.RECALL.getCode());
                item.setUpdateTime(new Date());
            });


            //将流程实例删除，并更新实例下运行中任务状态为撤销，环节状态为已完成
            processInstanceActionVo.getProcessInstanceIds().forEach(id -> runtimeService.deleteProcessInstance(id, "撤单"));
            updateRunningTaskStatusOrLinkStatus(processInstanceActionVo, processInstanceActionVo.getProcessInstanceIds(), ProcessInstanceStatusEnum.RECALL.getCode(), TaskLinkStatusEnum.DONE.getCode(), checkUser);
        }

        //终止
        if (Objects.equals(action, ProcessInstanceActionEnum.END.getCode())) {
            processInstanceTraces.forEach(item -> {
                item.setLinkStatus(ProcessInstanceLinkStatusEnum.END.getCode());
                item.setStatus(ProcessInstanceStatusEnum.END.getCode());
                item.setUpdateTime(new Date());
            });

            for (String id : processInstanceActionVo.getProcessInstanceIds()) {
//                try {
                List<Task> tasks = taskService.createTaskQuery().processInstanceId(id).list();
                for (Task task : tasks) {
                    try {  // 执行关闭流程后处理事件
                        this.executeNodeOperEvent(task.getId(), task.getAssignee(), NodeOperEnum.TERMINATE_PROCESS, NodeOperEventTypeEnum.AFTER);
                    } catch (Exception e) {
                        logger.error("execute terminate nodeOperEvent error:", e);
                        return ResponseResult.fail(ErrorCode.TERMINATE_PROCESS_FAIL.errCode(), id + "_" + task.getName() + "_" + e.getMessage());
                    }
                }
            }
            updateRunningTaskStatusOrLinkStatus(processInstanceActionVo, processInstanceActionVo.getProcessInstanceIds(), ProcessInstanceStatusEnum.END.getCode(), TaskLinkStatusEnum.DONE.getCode(), checkUser);
            //将流程实例删除，并更新实例下运行中任务状态为终止，环节状态为已完成
            processInstanceActionVo.getProcessInstanceIds().forEach(id -> runtimeService.deleteProcessInstance(id, "终止"));
              }


        //1.更新实例状态
        flowProcessInstanceTraceRepository.update(processInstanceTraces);
        doRecordOperateTrace(processInstanceActionVo, processInstanceTraces, action, checkUser);
        // 同步数据
        this.syncProcessTaskDataStatus(processInstanceActionVo);
        return ResponseResult.success();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean doActionWithoutUserCheck(ProcessInstanceActionVo processInstanceActionVo) {
        List<String> processInstanceIsList = new ArrayList<>(processInstanceActionVo.getProcessInstanceIds());
        List<FlowProcessInstanceTraceVo> flowProcessInstanceTraceVoList = flowProcessInstanceTraceRepository.getProcessInstanceTracesWithoutUserCheck(processInstanceIsList);
        if (Objects.nonNull(flowProcessInstanceTraceVoList) && !flowProcessInstanceTraceVoList.isEmpty()) {
            //不校验用户信息
            return doActionCommon(flowProcessInstanceTraceVoList, processInstanceActionVo, false).isSuccess();
        }
        return true;
    }

    /**
     * 任务实例操作
     *
     * @param flowTaskTraceVo
     * @param actionVo
     * @return
     */
    @Override
    public boolean doActionForTask(FlowTaskTraceVo flowTaskTraceVo, ProcessInstanceActionVo actionVo) {
        // 行为操作
        String action = actionVo.getAction();

        // 将任务实例暂停
        if (Objects.equals(action, ProcessInstanceActionEnum.PAUSE.getCode())) {
            // 更新flow_task_trace中的status状态字段
            updateTaskStatus(flowTaskTraceVo, actionVo, ProcessInstanceStatusEnum.PAUSE.getCode(), null);
            // 记录操作日志
            logOperate(flowTaskTraceVo, actionVo, ProcessInstanceStatusEnum.PAUSE.getCode());
        }

        // 将任务实例激活
        if (Objects.equals(action, ProcessInstanceActionEnum.UNPAUSE.getCode())) {
            //还原任务实例状态，根据flag, 还原实例下运行中的任务状态，以flag为准
            updateTaskStatusByFlag(flowTaskTraceVo, actionVo);
            // 记录操作日志
            logOperate(flowTaskTraceVo, actionVo, flagToStatus.get(flowTaskTraceVo.getFlag()));
        }

        // 处理成功,同步任务实例数据状态
        List<String> taskIds = new ArrayList<>();
        taskIds.add(flowTaskTraceVo.getTaskId());
        syncProcessTaskProcessor.syncUpdDbInfoByProcessOrTask(null, taskIds, actionVo.getAction());
        return true;
    }

    private void doRecordOperateTrace(ProcessInstanceActionVo
                                              processInstanceActionVo, List<FlowProcessInstanceTraceVo> processInstanceTraces, String action,
                                      boolean checkUser) {
        String userCode = null;
        String userName = null;
        if (checkUser) {
            User current = userRepository.getCurrentUser();
            userCode = current.getUserCode();
            userName = current.getUserName();
        } else {
            userCode = processInstanceActionVo.getUserCode();
            userName = processInstanceActionVo.getUserName();
        }

        //2.记录操作描述
        for (FlowProcessInstanceTraceVo trace : processInstanceTraces) {
            FlowProcessInstanceOperateTraceVo operateTrace = new FlowProcessInstanceOperateTraceVo();
            operateTrace.setAction(action);
            operateTrace.setProcessInstanceId(trace.getProcessInstanceId());
            operateTrace.setMessage(processInstanceActionVo.getComment());
            operateTrace.setOperatorId(userCode);
            operateTrace.setOperator(userName);

            flowProcessInstanceOperateTraceRepository.insertProcessInstanceOperateTrace(operateTrace);
        }
    }

    private void updateProcessInstanceToUnpause(Set<String> processInstanceIds) {
        processInstanceIds.forEach(item -> runtimeService.activateProcessInstanceById(item));
    }

    private void updateProcessInstanceToPause(Set<String> processInstanceIds) {
        processInstanceIds.forEach(item -> runtimeService.suspendProcessInstanceById(item));
    }

    private List<FlowTaskTraceVo> updateRunningTaskStatusByFlag(ProcessInstanceActionVo processInstanceActionVo,
                                                                boolean checkUser) {
        List<FlowTaskTraceVo> runningTaskTrace = null;
        if (checkUser) {
            runningTaskTrace = resolveRunningTask(processInstanceActionVo.getProcessInstanceIds());
        } else {
            runningTaskTrace = getRunningTaskTraceByProcessInstanceIds(processInstanceActionVo.getProcessInstanceIds());
        }
        runningTaskTrace.forEach(item -> {
            item.setStatus(flagToStatus.get(item.getFlag()));
            item.setUpdateTime(new Date());
            //记录操作人
            item.setOperatorCode(processInstanceActionVo.getUserCode());
            item.setOperator(processInstanceActionVo.getUserName());
            flowTaskTraceRepository.updateTaskTraceById(item);
            logOperate(item, processInstanceActionVo, processInstanceActionVo.getAction());
        });
        return runningTaskTrace;
    }

    private void updateRunningTaskStatusOrLinkStatus(ProcessInstanceActionVo
                                                             processInstanceActionVo, Set<String> processInstanceIds, String status, String linkStatus, boolean checkUser) {
        List<FlowTaskTraceVo> runningTaskTrace = null;
        if (checkUser) {
            runningTaskTrace = resolveRunningTask(processInstanceIds);
        } else {
            runningTaskTrace = getRunningTaskTraceByProcessInstanceIds(processInstanceActionVo.getProcessInstanceIds());
        }
        if (CollectionUtils.isNotEmpty(runningTaskTrace)) {
            runningTaskTrace.forEach(item -> {
                item.setStatus(status);
                item.setUpdateTime(new Date());
                //记录操作人
                item.setOperatorCode(processInstanceActionVo.getUserCode());
                item.setOperator(processInstanceActionVo.getUserName());
                if (StringUtils.isNotBlank(linkStatus)) {
                    item.setLinkStatus(linkStatus);
                }
                flowTaskTraceRepository.updateTaskTraceById(item);
                logOperate(item, processInstanceActionVo, status);
            });
        }
    }

    private void updateTaskStatus(FlowTaskTraceVo flowTaskTraceVo, ProcessInstanceActionVo actionVo, String
            status, String linkStatus) {
        flowTaskTraceVo.setStatus(status);
        flowTaskTraceVo.setUpdateTime(new Date());
        if (StringUtils.isNotBlank(linkStatus)) {
            flowTaskTraceVo.setLinkStatus(linkStatus);
        }
        flowTaskTraceRepository.updateTaskTraceById(flowTaskTraceVo);
    }

    private void updateTaskStatusByFlag(FlowTaskTraceVo flowTaskTraceVo, ProcessInstanceActionVo actionVo) {
        String status = flagToStatus.get(flowTaskTraceVo.getFlag());
        flowTaskTraceVo.setStatus(status);
        flowTaskTraceVo.setUpdateTime(new Date());
        flowTaskTraceRepository.updateTaskTraceById(flowTaskTraceVo);
    }


    private List<FlowTaskTraceVo> getRunningTaskTraceByProcessInstanceIds(Set<String> processInstanceIds) {
        TaskTraceQueryVo taskTraceQueryVo = new TaskTraceQueryVo();
        taskTraceQueryVo.setProcessInstanceIds(processInstanceIds);
        taskTraceQueryVo.setLinkStatus(TaskLinkStatusEnum.TODO.getCode());
        return flowTaskTraceRepository.listTaskTracesWithoutUserCheck(taskTraceQueryVo);
    }


    private void logOperate(FlowTaskTraceVo item, ProcessInstanceActionVo processInstanceActionVo, String status) {
        FlowCommonOperateLogVo log = new FlowCommonOperateLogVo();
        log.setTaskId(item.getTaskId());
        log.setType(status);
        log.setProcessInstanceId(item.getProcessInstanceId());
        log.setModelKey(item.getModelKey());
        log.setModelName(item.getModelName());
        log.setContent(processInstanceActionVo.getComment());
        log.setTaskName(item.getTaskName());
        log.setActivityId(item.getActivityId());
        commonOperateLogService.insertOperateLog(log);

    }

    private List<FlowTaskTraceVo> resolveRunningTask(Set<String> processInstanceIds) {
        TaskStatisticsQueryVo query = new TaskStatisticsQueryVo();
        query.setProcessInstanceIds(new ArrayList<>(processInstanceIds));
        query.setLinkStatus(TaskLinkStatusEnum.TODO.getCode());
        //FlowTaskTraceExample query = new FlowTaskTraceExample();
        //query.createCriteria().andProcessInstanceIdIn(new ArrayList<>(processInstanceIds)).andLinkStatusEqualTo(TaskLinkStatusEnum.TODO.getCode());
        return flowTaskTraceRepository.listTask(query);
    }

    private List<FlowProcessInstanceTraceVo> resolveRunningProcessInstances(Set<String> processInstanceIds) {
        ProcessInstanceStatisticsQueryVo query = new ProcessInstanceStatisticsQueryVo();
        query.setProcessInstanceIds(processInstanceIds);
        query.setLinkStatuses(Arrays.asList(ProcessInstanceLinkStatusEnum.RUNNING.getCode(), ProcessInstanceLinkStatusEnum.START.getCode()));
        //FlowProcessInstanceTraceExample query = new FlowProcessInstanceTraceExample();
        //query.createCriteria().andProcessInstanceIdIn(new ArrayList<>(processInstanceIds)).andLinkStatusIn(Arrays.asList(ProcessInstanceLinkStatusEnum.RUNNING.getCode(), ProcessInstanceLinkStatusEnum.START.getCode()));
        return flowProcessInstanceTraceRepository.getProcessInstanceTraces(query);
    }

    /**
     * 同步流程实例或任务实例数据状态
     *
     * @param processInstanceActionVo
     */
    private void syncProcessTaskDataStatus(ProcessInstanceActionVo processInstanceActionVo) {
        // 处理成功,同步流程实例数据状态
        List<String> processInstanceIds = new ArrayList<>();
        processInstanceIds.addAll(processInstanceActionVo.getProcessInstanceIds());
        syncProcessTaskProcessor.syncUpdDbInfoByProcessOrTask(processInstanceIds, null, processInstanceActionVo.getAction());
    }
}
